﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Xml;
using System.Xml.Linq;
using System.Xml.XPath;
using log4net;

namespace StatesConverter.Logic
{
	public class XGmlProcessor
	{
		private readonly ILog logger = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
		private readonly string savePath;
		private readonly Stream xmlStream;


		public XGmlProcessor(
			Stream xml,
			string savePath)
		{
			this.xmlStream = xml;
			this.savePath = savePath;
		}


		public void StartProcessing()
		{
			//Key is the StateID, value is the element representing the state
			Dictionary<int, XElement> states = new Dictionary<int, XElement>();

			//Key is the id of the node, Value is the stateID
			Dictionary<int, int> transitionLookup = new Dictionary<int, int>();

			//key is the id of the node, value is the transition name
			Dictionary<int, string> transitionGroupLookup = new Dictionary<int, string>();

			//skip the xml declaration.
			this.xmlStream.Position = 41;

			XmlReader reader = XmlReader.Create(this.xmlStream);

			XDocument doc = XDocument.Load(reader);

			//IEnumerable<XElement> elements = doc.Elements("section").Attributes("node");

			IEnumerable<XElement> nodes =
				from el in doc.Descendants("section")
				where (string)el.Attribute("name") == "node"
				select el;


			foreach (var node in nodes)
			{
				//figure out what kind of an action is this
				string type =
					node.XPathSelectElements(".//section[@name='graphics']//attribute[@key='type']").First().Value;

				string contents =
					node.XPathSelectElements(".//section[@name='LabelGraphics']//attribute[@key='text']").First().Value;

				int nodeId
					= int.Parse(node.XPathSelectElements(".//attribute[@key='id']").First().Value);

				int stateId;

				switch (type)
				{
					case "rectangle":
						XElement pageNode = this.CreatePageNode(out stateId, contents);
						states[stateId] = pageNode;
						transitionLookup[nodeId] = stateId;
						break;
					case "roundrectangle":
						XElement actionNode = this.CreateActionNode(out stateId, contents);
						states[stateId] = actionNode;
						transitionLookup[nodeId] = stateId;
						break;
					case "ellipse":
						transitionLookup[nodeId] = int.Parse(contents);
						break;
					case "diamond":
						transitionGroupLookup[nodeId] = contents;
						//transitionLookup[nodeId] = -1;
						break;
					case "octagon":
						XElement stopNode = this.CreateStopNode(out stateId, contents);
						states[stateId] = stopNode;
						transitionLookup[nodeId] = stateId;
						break;
				}
			}

			IEnumerable<XElement> edges =
				from el in doc.Descendants("section")
				where (string)el.Attribute("name") == "edge"
				select el;

			foreach (var edge in edges)
			{
				string transitions =
					edge.XPathSelectElements(".//section[@name='LabelGraphics']//attribute[@key='text']").First().Value;

				int fromNodeId =
					int.Parse(edge.XPathSelectElements(".//attribute[@key='source']").First().Value);

				int toNodeId =
					int.Parse(edge.XPathSelectElements(".//attribute[@key='target']").First().Value);

				int fromStateId = transitionLookup[fromNodeId];

				if (transitionGroupLookup.ContainsKey(toNodeId))
				{
					string[] contents = transitionGroupLookup[toNodeId].Split('\n');

					/*foreach (string transition in transitionSplit) {
						states[fromStateId].Add(
							new XElement("transitionGroup",
							             new XAttribute("name", transition))
							);
					}*/

					Dictionary<string, string> content = this.ProcessContent(contents);

					XElement transitionGroupElement = new XElement("transitionGroup");

					transitionGroupElement = this.ProcessAttributes(transitionGroupElement, content);

					states[fromStateId].Add(transitionGroupElement);
				}
				else
				{
					int toStateId = transitionLookup[toNodeId];

					string[] transitionSplit = transitions.Split('\n');

					foreach (string transition in transitionSplit)
					{
						states[fromStateId].Add(
							new XElement("transition",
										 new XAttribute("name", transition),
										 new XAttribute("targetState", toStateId))
							);
					}
				}
			}


			//compile the final states config
			XElement superStates = new XElement("states");

			var keys = states.Keys.OrderBy(x => x);

			foreach (int key in keys)
			{
				superStates.Add(states[key]);
			}

			XDocument finalDoc = new XDocument(
				new XDeclaration("1.0", "utf-8", "yes"),
				superStates
				);

			finalDoc.Save(this.savePath);

			this.xmlStream.Close();
		}


		private XElement CreateStopNode(
			out int stateId,
			string contents)
		{
			string[] split = contents.Split('\n');

			Dictionary<string, string> attributes = this.ProcessContent(split);

			XElement state = new XElement("state");

			if (!attributes.ContainsKey("id"))
			{
				stateId = int.Parse(split[0]);
				attributes["id"] = split[0];
			}
			else
			{
				stateId = int.Parse(attributes["id"]);
			}

			if (!attributes.ContainsKey("type"))
			{
				attributes["type"] = "xmlend";
			}

			state = this.ProcessAttributes(state, attributes);

			return state;
		}


		private XElement CreatePageNode(
			out int stateId,
			string contents)
		{
			string[] split = contents.Split('\n');

			Dictionary<string, string> attributes = this.ProcessContent(split);

			XElement state = new XElement("state", new XAttribute("type", "page"));

			if (!attributes.ContainsKey("id"))
			{
				stateId = int.Parse(split[0]);
				attributes["id"] = split[0];
			}
			else
			{
				stateId = int.Parse(attributes["id"]);
			}

			if (!attributes.ContainsKey("name"))
			{
				attributes["name"] = split[1];
			}

			if (!attributes.ContainsKey("page"))
			{
				attributes["page"] = split[1];
			}

			state = this.ProcessAttributes(state, attributes);

			return state;
		}


		private XElement CreateActionNode(
			out int stateId,
			string contents)
		{
			string[] split = contents.Split('\n');

			Dictionary<string, string> attributes = this.ProcessContent(split);

			XElement state = new XElement("state", new XAttribute("type", "action"));

			if (!attributes.ContainsKey("id"))
			{
				stateId = int.Parse(split[0]);
				attributes["id"] = split[0];
			}
			else
			{
				stateId = int.Parse(attributes["id"]);
			}

			if (!attributes.ContainsKey("name"))
			{
				attributes["name"] = split[1];
			}

			if (!attributes.ContainsKey("action"))
			{
				attributes["action"] = split[1];
			}

			state = this.ProcessAttributes(state, attributes);

			return state;
		}


		private XElement ProcessAttributes(
			XElement state,
			Dictionary<string, string> attributes)
		{
			foreach (string key in attributes.Keys)
			{
				state.SetAttributeValue(key, attributes[key]);
			}

			return state;
		}


		private Dictionary<string, string> ProcessContent(string[] contents)
		{
			Dictionary<string, string> keyVal = new Dictionary<string, string>();

			foreach (string content in contents)
			{
				try
				{
					string newcontent = content;
					//remove the brackets
					if (content.StartsWith("["))
					{
						newcontent = content.Remove(0, 1);
						newcontent = newcontent.Remove(newcontent.Length - 1, 1);
					}
					string[] split = newcontent.Split(':');

					keyVal[split[0]] = split[1];
				}
				catch (Exception ex)
				{
					//suppress all errors
					this.logger.Error("process Content", ex);
				}
			}

			return keyVal;
		}
	}
}
